/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tencent.tendon.convert.json;

import com.tencent.tendon.convert.util.Creator;
import com.tencent.tendon.convert.util.CreatorFields;
import java.util.*;
import java.lang.reflect.*;

/**
 *
 * @author nbzhang
 */
class JsonLoader {

    private JsonLoader() {
    }
    //-----------------------------------------decoder--------------------------------------------------------------------------------

    public static JsonDecodable loadDecodable(final Type type) {
        JsonDecoder decoder = JsonFactory.findDecoder(type);
        if (decoder != null) return decoder;
        Class clazz = null;
        if (type instanceof ParameterizedType) {
            final ParameterizedType pts = (ParameterizedType) type;
            clazz = (Class) (pts).getRawType();
        } else if (type instanceof Class) {
            clazz = (Class) type;
        } else if (type instanceof GenericArrayType) {
            decoder = new JsonArrayDecoder(type);
            JsonFactory.register(type, decoder);
            return decoder;
        } else {
            throw new JsonException("not support the type (" + type + ")");
        }
        JsonListener listener = JsonFactory.findListener(clazz);
        if (listener != null) return listener;
        decoder = JsonFactory.findDecoder(clazz);
        if (decoder != null) return decoder;
        if (clazz.isArray()) {
            decoder = new JsonArrayDecoder(type);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            decoder = new JsonCollectionDecoder(type);
        } else if (Map.class.isAssignableFrom(clazz)) {
            decoder = new JsonMapDecoder(type);
        } else if (!clazz.getName().startsWith("java.")) {
            decoder = createJsonObjectDecoder(clazz);
        } else {
            throw new JsonException("not support the type (" + type + ")");
        }
        JsonFactory.register(type, decoder);
        return decoder;
    }

    private static JsonObjectDecoder createJsonObjectDecoder(final Class clazz) {
        final JsonObjectDecoder entity = new JsonObjectDecoder(clazz);
        JsonFactory.register(clazz, entity);
        entity.setJsonInterceptor(JsonFactory.findInterceptor(clazz));
        final List<JsonDeHandle> handles = new ArrayList();

        for (final AccessibleObject fitem : loadAccessibleObject(clazz, true)) {
            final Type t = JsonAttributes.findFieldType(fitem);
            String fieldname = (fitem instanceof Field) ? ((Field) fitem).getName() : JsonAttributes.realMethodName((Method) fitem);
            final JsonField jf = JsonFactory.findField(clazz, fieldname);
            final JsonAttribute attribute = JsonAttributes.createJsonAttribute(fitem);
            Type ft = jf == null ? null : jf.type();
            JsonRef ref = fitem.getAnnotation(JsonRef.class);
            JsonDecodable decoder;
            if (ref != null && ref.listener() != JsonListener.class) {
                try {
                    decoder = ref.listener().newInstance();
                } catch (Exception ex) {
                    throw new JsonException("illegal listener (" + ref.listener() + ") for " + fitem, ex);
                }
            } else if (jf != null && jf.listener() != null) {
                decoder = jf.listener();
            } else {
                decoder = loadDecodable(ft == null ? t : ft);
            }
            handles.add(JsonDeHandle.create(attribute, decoder));
        }
        final JsonDeHandle head = JsonDeHandle.link(handles);
        entity.setJsonDeHandle(head);
        JsonDeHandle handle = head;
        boolean simpled = true;
        do {
            simpled = simpled && handle.isSimpled();
        } while ((handle = handle.next) != null);
        entity.simpled = simpled;
        return entity;
    }

    //-----------------------------------------encoder--------------------------------------------------------------------------------
    public static JsonEncodable loadEncodable(final Type type) {
        JsonEncoder encoder = JsonFactory.findEncoder(type);
        if (encoder != null) return encoder;
        Class clazz;
        if (type instanceof ParameterizedType) {
            final ParameterizedType pts = (ParameterizedType) type;
            clazz = (Class) (pts).getRawType();
        } else if (type instanceof Class) {
            clazz = (Class) type;
        } else if (type instanceof GenericArrayType) {
            encoder = new JsonArrayEncoder(type);
            JsonFactory.register(type, encoder);
            return encoder;
        } else {
            return JsonAnyEncoder.getInstance();
        }
        JsonListener listener = JsonFactory.findListener(clazz);
        if (listener != null) return listener;
        encoder = JsonFactory.findEncoder(clazz);
        if (encoder != null) return encoder;
        if (clazz.isArray()) {
            encoder = new JsonArrayEncoder(type);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            encoder = new JsonCollectionEncoder(type);
        } else if (Map.class.isAssignableFrom(clazz)) {
            encoder = new JsonMapEncoder(type);
        } else if (!clazz.getName().startsWith("java.")) {
            encoder = createJsonObjectEncoder(clazz);
        } else {
            encoder = JsonAnyEncoder.getInstance();
        }
        JsonFactory.register(type, encoder);
        return encoder;
    }

    private static JsonObjectEncoder createJsonObjectEncoder(final Class clazz) {
        final JsonObjectEncoder entity = new JsonObjectEncoder(clazz);
        JsonFactory.register(clazz, entity);
        entity.setJsonInterceptor(JsonFactory.findInterceptor(clazz));
        final List<JsonEnHandle> handles = new ArrayList();

        for (final AccessibleObject fitem : loadAccessibleObject(clazz, false)) {
            final Type t = JsonAttributes.findFieldType(fitem);
            String fieldname = (fitem instanceof Field) ? ((Field) fitem).getName() : JsonAttributes.realMethodName((Method) fitem);
            final JsonField jf = JsonFactory.findField(clazz, fieldname);
            final JsonAttribute attribute = JsonAttributes.createJsonAttribute(fitem);
            Type ft = jf == null ? null : jf.type();
            JsonRef ref = fitem.getAnnotation(JsonRef.class);
            JsonEncodable encoder;
            if (ref != null && ref.listener() != JsonListener.class) {
                try {
                    encoder = ref.listener().newInstance();
                } catch (Exception ex) {
                    throw new JsonException("illegal listener (" + ref.listener() + ") for " + fitem, ex);
                }
            } else if (jf != null && jf.listener() != null) {
                encoder = jf.listener();
            } else {
                encoder = loadEncodable(ft == null ? t : ft);
            }
            handles.add(JsonEnHandle.create(attribute, encoder));
        }
        final JsonEnHandle head = JsonEnHandle.link(handles);
        entity.setJsonEnHandle(head);
        JsonEnHandle handle = head;
        boolean simpled = true;
        do {
            simpled = simpled && handle.isSimpled();
        } while ((handle = handle.next) != null);
        entity.simpled = simpled;
        return entity;
    }
    //--------------------------------------------------------------------------------------------------------------------------------

    private static List<AccessibleObject> loadAccessibleObject(final Class clazz, boolean setable) {
        final List<AccessibleObject> list = new ArrayList<AccessibleObject>();
        final HashSet<String> fields = new HashSet();
        String[] constructorFields = null;
        if (setable) {
            Creator creator = JsonFactory.findCreator(clazz);
            if (creator != null) {
                try {
                    CreatorFields cf = creator.getClass().getMethod("create", Object[].class).getAnnotation(CreatorFields.class);
                    if (cf != null) constructorFields = cf.value();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        for (final Field field : clazz.getFields()) {
            if (Modifier.isStatic(field.getModifiers())) continue;
            if ((setable && (constructorFields == null || Arrays.binarySearch(constructorFields, field.getName()) < 0)) && Modifier.isFinal(field.getModifiers())) continue;
            JsonRef ref = field.getAnnotation(JsonRef.class);
            if (ref != null && ref.ignore()) continue;
            JsonField jf = JsonFactory.findField(clazz, field.getName());
            if (jf != null && jf.ignore()) continue;
            fields.add(field.getName());
            list.add(field);
        }
        for (final Method method : clazz.getMethods()) {
            if (Modifier.isStatic(method.getModifiers())) continue;
            if (Modifier.isAbstract(method.getModifiers())) continue;
            if (setable) {
                if (method.getName().length() < 4) continue;
                if (!method.getName().startsWith("set")) continue;
                if (method.getParameterTypes().length != 1) continue;
            } else {
                if (method.getName().length() < 3) continue;
                if (method.getName().equals("getClass")) continue;
                if (!method.getName().startsWith("is") && !method.getName().startsWith("get")) continue;
                if (method.getParameterTypes().length != 0) continue;
                if (method.getReturnType() == void.class) continue;
            }
            final String fieldname = JsonAttributes.realMethodName(method);
            if (fields.contains(fieldname)) continue;
            if (fieldname.length() < 1) continue;
            JsonRef ref = method.getAnnotation(JsonRef.class);
            if (ref != null && ref.ignore()) continue;
            JsonField jf = JsonFactory.findField(clazz, fieldname);
            if (jf != null && jf.ignore()) continue;
            list.add(method);
        }
        return list;
    }
}
